/////////////////////////////////////////////////////////////////////////////////////////////
// Filename: WindowsClass.cpp
// (c) Dave Ottley, 2012. All rights reserved.
/////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////
//		INCLUDES
///////////////////////////
#include <Windows.h>
#include <WindowsX.h>
#include <assert.h>

///////////////////////////
//		MY INCLUDES
///////////////////////////
#include "WindowsClass.h"
#include "Constants.h"
#include "WindowsOwnedStates.h"
#include "Icon.h"
#include "Util.h"
#include "SubsystemID.h"
#include "Messages.h"
#include "ApplicationLayer.h"


// Global callback function
// Calls class version.
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	

	return WindowsCls->MessageHandler(hWnd, uMsg, wParam, lParam);
}

/////////////////////////////////////////////////////////////////////////////////////////////
// 
//						CONSTRUCTOR / GET INSTANCE
//
/////////////////////////////////////////////////////////////////////////////////////////////
WindowsClass::WindowsClass() :
	mnCmdShow(0), mhInstance(0), mhWnd(0), mID(SubsystemID::WINDOWS)
{

	//Initialize Variables
	mScreenWidth = SCREEN_WIDTH;	
	mScreenHeight = SCREEN_HEIGHT;
	mhInstance = 0;
	mhWnd = 0;

	// Setup the State Machine
	mStateMachine = new StateMachine<WindowsClass>(this);
	mStateMachine->SetCurrentState( WindowsNormalState::GetInstance() );
	mStateMachine->SetGlobalState( WindowsGlobalState::GetInstance() );
}



/////////////////////////////////////////////////////////////////////////////////////////////
// 
//						INITIALIZE
//
/////////////////////////////////////////////////////////////////////////////////////////////
bool WindowsClass::Initialize(HINSTANCE hInstance, int nCmdShow)
{
	//Create variables
	WNDCLASSEX myClass;

	//Initialization
	mhInstance = hInstance;
	mnCmdShow = nCmdShow;
	
	//Set Windows Options
	myClass.cbClsExtra = 0;
	myClass.cbSize = sizeof(WNDCLASSEX);
	myClass.cbWndExtra = 0;
	myClass.hbrBackground = (HBRUSH)GetStockObject( COLOR_WINDOW + 1 ); //BLACK_BRUSH);
	myClass.hCursor = LoadCursor(NULL, IDC_ARROW); // hide anyway
	myClass.hIcon = LoadIcon(mhInstance, MAKEINTRESOURCE(IDC_CUSTOMICON));
	myClass.hIconSm = LoadIcon(mhInstance, MAKEINTRESOURCE(IDC_CUSTOMICON));
	myClass.hInstance = mhInstance;
	myClass.lpfnWndProc = WndProc;
	myClass.lpszClassName = WINDOWS_CLASS_NAME;
	myClass.lpszMenuName = APPLICATION_TITLE;
	myClass.style = CS_HREDRAW | CS_VREDRAW;

	//Register Windows Class
	if(!RegisterClassEx(&myClass))
	{
		MessageBox(mhWnd, L"Windows Class Registration Failed", L"Error", NULL);
		return false;
	}

	//Create Window: Adjust window size to accommodate the proper client rectangle
	DWORD windowStyle = 
		WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX;
	RECT rc = { 0, 0, mScreenWidth, mScreenHeight };
	AdjustWindowRect( &rc, windowStyle, false );

	mhWnd = CreateWindow(myClass.lpszClassName, 
						myClass.lpszMenuName,
						windowStyle,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						rc.right - rc.left,
						rc.bottom - rc.top,
						NULL,
						NULL,
						mhInstance,
						NULL);

	if(!mhWnd)
	{
		MessageBox(mhWnd, L"Window Creation Failed", L"Error", NULL);
		return false;
	}

	//Show Window
	ShowWindow(mhWnd, mnCmdShow);

	//Update Window
	UpdateWindow(mhWnd);
	
	//hide the cursor
	ShowCursor(SHOW_CURSOR);

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// 
//						MESSAGE HANDLER
//
/////////////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WindowsClass::MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	//When the Window is being closed
	case WM_CLOSE:
		PostQuitMessage(0);
		return 0;
		
	// WM_DESTROY is sent when the window is being destroyed.
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	case WM_MENUCHAR:
        // Don't beep when we alt-enter.
        return MAKELRESULT(0, MNC_CLOSE);

	// WM_ACTIVATE is sent when the window is activated or deactivated.  
	// We pause the game when the window is deactivated and unpause it 
	// when it becomes active.  
	case WM_ACTIVATE:
		if( LOWORD(wParam) == WA_INACTIVE )
		{
			assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::PauseRequest, NO_EXTRA_INFO) 
				&& "<ApplicationMsg::PauseRequest> Unable to pause application.");
		}
		else
		{
			assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::UnpauseRequest, NO_EXTRA_INFO) 
				&& "<ApplicationMsg::UnpauseRequest> Unable to unpause application.");
		}
		return 0;

	// WM_SIZE is sent when the user resizes the window.  
	case WM_SIZE:
		// Save the new client area dimensions.
		mScreenWidth  = LOWORD(lParam);
		mScreenHeight = HIWORD(lParam);
		
		bool isActive;
		assert(Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::IsDeviceActive, &isActive) 
				&& "<GraphicsMsg::IsDeviceActive> Message not handled properly.");
		if( isActive )
		{
			if( wParam == SIZE_MINIMIZED )
			{
				mMinimized = true;
				mMaximized = false;

				assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::PauseRequest, NO_EXTRA_INFO) 
					&& "<ApplicationMsg::PauseRequest> Unable to pause application.");
				
			}
			else if( wParam == SIZE_MAXIMIZED )
			{
				mMinimized = false;
				mMaximized = true;

				assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::UnpauseRequest, NO_EXTRA_INFO) 
					&& "<ApplicationMsg::UnpauseRequest> Unable to unpause application.");
				
				assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::WindowResized, NO_EXTRA_INFO)
						&& "<GraphicsMsg::WindowResized> Graphics system could not resize window buffers"); 
				//D3DCls->OnResize();
			}
			else if( wParam == SIZE_RESTORED )
			{
				
				// Restoring from minimized state?
				if( mMinimized )
				{
					mMinimized = false;

					assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::UnpauseRequest, NO_EXTRA_INFO) 
					&& "<ApplicationMsg::UnpauseRequest> Unable to unpause application.");
					
					assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::WindowResized, NO_EXTRA_INFO)
						&& "<GraphicsMsg::WindowResized> Graphics system could not resize window buffers"); 
				}

				// Restoring from maximized state?
				else if( mMaximized )
				{
					mMaximized = false;

					assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::APPLICATION, ApplicationMsg::UnpauseRequest, NO_EXTRA_INFO) 
						&& "<ApplicationMsg::UnpauseRequest> Unable to unpause application.");
					
					assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::WindowResized, NO_EXTRA_INFO)
						&& "<GraphicsMsg::WindowResized> Graphics system could not resize window buffers"); 
				}
				else if( mResizing )
				{
					// If user is dragging the resize bars, we do not resize 
					// the buffers here because as the user continuously 
					// drags the resize bars, a stream of WM_SIZE messages are
					// sent to the window, and it would be pointless (and slow)
					// to resize for each WM_SIZE message received from dragging
					// the resize bars.  So instead, we reset after the user is 
					// done resizing the window and releases the resize bars, which 
					// sends a WM_EXITSIZEMOVE message.
				}
				else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
				{
					assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::WindowResized, NO_EXTRA_INFO)
						&& "<GraphicsMsg::WindowResized> Graphics system could not resize window buffers"); 
				}
			}
		}
		return 0;

	// WM_EXITSIZEMOVE is sent when the user grabs the resize bars.
	case WM_ENTERSIZEMOVE:
		//ApplicationCls->Pause();
		mResizing  = true;
		return 0;

	// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
	// Here we reset everything based on the new window dimensions.
	case WM_EXITSIZEMOVE:
		//ApplicationCls->Unpause();
		mResizing  = false;
		assert( Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::WindowResized, NO_EXTRA_INFO)
			&& "<GraphicsMsg::WindowResized> Graphics system could not resize window buffers"); 
		return 0;

	// Catch this message so to prevent the window from becoming too small.
	case WM_GETMINMAXINFO:
		((MINMAXINFO*)lParam)->ptMinTrackSize.x = MIN_X_SIZE;
		((MINMAXINFO*)lParam)->ptMinTrackSize.y = MIN_Y_SIZE; 
		return 0;

	case WM_KEYDOWN:
		WPARAM tempWParam = wParam;
		Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::INPUT, InputMsg::KeyDown, tempWParam);
		g_pApp->Handle
		break;

	// WM_SYSKEYDOWN used to detect alt-enter.
	// Toggle Fullscreen
	case WM_SYSKEYDOWN:              
		if (wParam == VK_RETURN)
			if ((HIWORD(lParam) & KF_ALTDOWN))
			{
				//D3DCls->ToggleFullscreen();
				assert(Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::GRAPHICS, GraphicsMsg::ToggleFullscreen, NO_EXTRA_INFO)
					&& "<GraphicsMsg::ToggleFullscreen> Graphics system could not toggle fullscreen"); 
				return 0;
			}
		break;

	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		{
			int mousePosition[2] = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			//InputCls->MouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::INPUT, InputMsg::MouseDown, mousePosition);
			return 0;
		}
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		{
			int mousePosition[2] = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			//InputCls->MouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::INPUT, InputMsg::MouseUp, mousePosition);
			return 0;
		}
	case WM_MOUSEMOVE:
		{
			int mousePosition[2] = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			//InputCls->MouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, GetID(), SubsystemID::INPUT, InputMsg::MouseMove, mousePosition);
			return 0;
		}
		
	} // switch(msg)

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}